//*****************************************************************************
//
//! \file dhcp.c
//! \brief DHCP APIs implement file.
//! \details Processing DHCP protocol as DISCOVER, OFFER, REQUEST, ACK, NACK and
//! DECLINE. \version 1.1.1 \date 2019/10/08 \par  Revision history
//!       <2019/10/08> compare DHCP server ip address
//!       <2013/11/18> 1st Release
//!       <2012/12/20> V1.1.0
//!         1. Optimize code
//!         2. Add reg_dhcp_cbfunc()
//!         3. Add DHCP_stop()
//!         4. Integrate check_DHCP_state() & DHCP_run() to DHCP_run()
//!         5. Don't care system endian
//!         6. Add comments
//!       <2012/12/26> V1.1.1
//!         1. Modify variable declaration: dhcp_tick_1s is declared volatile
//!         for code optimization
//! \author Eric Jung & MidnightCow
//! \copyright
//!
//! Copyright (c)  2013, WIZnet Co., LTD.
//! All rights reserved.
//!
//! Redistribution and use in source and binary forms, with or without
//! modification, are permitted provided that the following conditions
//! are met:
//!
//!     * Redistributions of source code must retain the above copyright
//! notice, this list of conditions and the following disclaimer.
//!     * Redistributions in binary form must reproduce the above copyright
//! notice, this list of conditions and the following disclaimer in the
//! documentation and/or other materials provided with the distribution.
//!     * Neither the name of the <ORGANIZATION> nor the names of its
//! contributors may be used to endorse or promote products derived
//! from this software without specific prior written permission.
//!
//! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//! AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//! ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
//! LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//! CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//! SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//! INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//! CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//! ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
//! THE POSSIBILITY OF SUCH DAMAGE.
//
//*****************************************************************************



#include "../socket.h"
#include "dhcp.h"
/* If you want to display debug & processing message, Define _DHCP_DEBUG_ in
 * dhcp.h */

#ifdef _DHCP_DEBUG_
#include <stdio.h>
#endif

/* DHCP state machine. */
#define STATE_DHCP_INIT 0       ///< Initialize
#define STATE_DHCP_DISCOVER 1   ///< send DISCOVER and wait OFFER
#define STATE_DHCP_REQUEST 2    ///< send REQEUST and wait ACK or NACK
#define STATE_DHCP_LEASED 3     ///< ReceiveD ACK and IP leased
#define STATE_DHCP_REREQUEST 4  ///< send REQUEST for maintaining leased IP
#define STATE_DHCP_RELEASE 5    ///< No use
#define STATE_DHCP_STOP 6       ///< Stop processing DHCP

#define DHCP_FLAGSBROADCAST \
  0x8000  ///< The broadcast value of flags in @ref RIP_MSG
#define DHCP_FLAGSUNICAST \
  0x0000  ///< The unicast   value of flags in @ref RIP_MSG

/* DHCP message OP code */
#define DHCP_BOOTREQUEST 1  ///< Request Message used in op of @ref RIP_MSG
#define DHCP_BOOTREPLY 2    ///< Reply Message used i op of @ref RIP_MSG

/* DHCP message type */
#define DHCP_DISCOVER 1  ///< DISCOVER message in OPT of @ref RIP_MSG
#define DHCP_OFFER 2     ///< OFFER message in OPT of @ref RIP_MSG
#define DHCP_REQUEST 3   ///< REQUEST message in OPT of @ref RIP_MSG
#define DHCP_DECLINE 4   ///< DECLINE message in OPT of @ref RIP_MSG
#define DHCP_ACK 5       ///< ACK message in OPT of @ref RIP_MSG
#define DHCP_NAK 6       ///< NACK message in OPT of @ref RIP_MSG
#define DHCP_RELEASE 7   ///< RELEASE message in OPT of @ref RIP_MSG. No use
#define DHCP_INFORM 8    ///< INFORM message in OPT of @ref RIP_MSG. No use

#define DHCP_HTYPE10MB 1   ///< Used in type of @ref RIP_MSG
#define DHCP_HTYPE100MB 2  ///< Used in type of @ref RIP_MSG

#define DHCP_HLENETHERNET 6  ///< Used in hlen of @ref RIP_MSG
#define DHCP_HOPS 0          ///< Used in hops of @ref RIP_MSG
#define DHCP_SECS 0          ///< Used in secs of @ref RIP_MSG

#define INFINITE_LEASETIME 0xffffffff  ///< Infinite lease time

/*
 * @brief DHCP option and value (cf. RFC1533)
 */
enum {
  padOption = 0,
  subnetMask = 1,
  timerOffset = 2,
  routersOnSubnet = 3,
  timeServer = 4,
  nameServer = 5,
  dns = 6,
  logServer = 7,
  cookieServer = 8,
  lprServer = 9,
  impressServer = 10,
  resourceLocationServer = 11,
  hostName = 12,
  bootFileSize = 13,
  meritDumpFile = 14,
  domainName = 15,
  swapServer = 16,
  rootPath = 17,
  extentionsPath = 18,
  IPforwarding = 19,
  nonLocalSourceRouting = 20,
  policyFilter = 21,
  maxDgramReasmSize = 22,
  defaultIPTTL = 23,
  pathMTUagingTimeout = 24,
  pathMTUplateauTable = 25,
  ifMTU = 26,
  allSubnetsLocal = 27,
  broadcastAddr = 28,
  performMaskDiscovery = 29,
  maskSupplier = 30,
  performRouterDiscovery = 31,
  routerSolicitationAddr = 32,
  staticRoute = 33,
  trailerEncapsulation = 34,
  arpCacheTimeout = 35,
  ethernetEncapsulation = 36,
  tcpDefaultTTL = 37,
  tcpKeepaliveInterval = 38,
  tcpKeepaliveGarbage = 39,
  nisDomainName = 40,
  nisServers = 41,
  ntpServers = 42,
  vendorSpecificInfo = 43,
  netBIOSnameServer = 44,
  netBIOSdgramDistServer = 45,
  netBIOSnodeType = 46,
  netBIOSscope = 47,
  xFontServer = 48,
  xDisplayManager = 49,
  dhcpRequestedIPaddr = 50,
  dhcpIPaddrLeaseTime = 51,
  dhcpOptionOverload = 52,
  dhcpMessageType = 53,
  dhcpServerIdentifier = 54,
  dhcpParamRequest = 55,
  dhcpMsg = 56,
  dhcpMaxMsgSize = 57,
  dhcpT1value = 58,
  dhcpT2value = 59,
  dhcpClassIdentifier = 60,
  dhcpClientIdentifier = 61,
  endOption = 255
};

uint8_t DHCP_SOCKET;  // Socket number for DHCP

uint8_t DHCP_SIP[4];       // DHCP Server IP address
uint8_t DHCP_REAL_SIP[4];  // For extract my DHCP server in a few DHCP server

// Network information from DHCP Server
uint8_t OLD_allocated_ip[4] = {
    0,
};  // Previous IP address
uint8_t DHCP_allocated_ip[4] = {
    0,
};  // IP address from DHCP
uint8_t DHCP_allocated_gw[4] = {
    0,
};  // Gateway address from DHCP
uint8_t DHCP_allocated_sn[4] = {
    0,
};  // Subnet mask from DHCP
uint8_t DHCP_allocated_dns[4] = {
    0,
};  // DNS address from DHCP

int8_t dhcp_state = STATE_DHCP_STOP;  // DHCP state
int8_t dhcp_retry_count = 0;

uint32_t dhcp_lease_time = INFINITE_LEASETIME;
volatile uint32_t dhcp_tick_1s = 0;  // unit 1 second
uint32_t dhcp_tick_next = DHCP_WAIT_TIME;

uint32_t DHCP_XID;  // Any number

RIP_MSG *pDHCPMSG;  // Buffer pointer for DHCP processing

uint8_t HOST_NAME[] = DCHP_HOST_NAME;

uint8_t DHCP_CHADDR[6];  // DHCP Client MAC address.

/* The default callback function */
void default_ip_assign(void);
void default_ip_update(void);
void default_ip_conflict(void);

/* Callback handler */
void (*dhcp_ip_assign)(void) =
    default_ip_assign; /* handler to be called when the IP address from DHCP
                          server is first assigned */
void (*dhcp_ip_update)(void) =
    default_ip_update; /* handler to be called when the IP address from DHCP
                          server is updated */
void (*dhcp_ip_conflict)(void) =
    default_ip_conflict; /* handler to be called when the IP address from DHCP
                            server is conflict */

void reg_dhcp_cbfunc(void (*ip_assign)(void), void (*ip_update)(void),
                     void (*ip_conflict)(void));

char NibbleToHex(uint8_t nibble);

/* send DISCOVER message to DHCP server */
void send_DHCP_DISCOVER(void);

/* send REQEUST message to DHCP server */
void send_DHCP_REQUEST(void);

/* send DECLINE message to DHCP server */
void send_DHCP_DECLINE(void);

/* IP conflict check by sending ARP-request to leased IP and wait ARP-response.
 */
int8_t check_DHCP_leasedIP(void);

/* check the timeout in DHCP process */
uint8_t check_DHCP_timeout(void);

/* Initialize to timeout process.  */
void reset_DHCP_timeout(void);

/* Parse message as OFFER and ACK and NACK from DHCP server.*/
int8_t parseDHCPCMSG(void);

/* The default handler of ip assign first */
void default_ip_assign(void) {
  setSIPR(DHCP_allocated_ip);
  setSUBR(DHCP_allocated_sn);
  setGAR(DHCP_allocated_gw);
}

/* The default handler of ip changed */
void default_ip_update(void) {
  /* WIZchip Software Reset */
  setMR(MR_RST);
  getMR();  // for delay
  default_ip_assign();
  setSHAR(DHCP_CHADDR);
}

/* The default handler of ip changed */
void default_ip_conflict(void) {
  // WIZchip Software Reset
  setMR(MR_RST);
  getMR();  // for delay
  setSHAR(DHCP_CHADDR);
}

/* register the call back func. */
void reg_dhcp_cbfunc(void (*ip_assign)(void), void (*ip_update)(void),
                     void (*ip_conflict)(void)) {
  dhcp_ip_assign = default_ip_assign;
  dhcp_ip_update = default_ip_update;
  dhcp_ip_conflict = default_ip_conflict;
  if (ip_assign) dhcp_ip_assign = ip_assign;
  if (ip_update) dhcp_ip_update = ip_update;
  if (ip_conflict) dhcp_ip_conflict = ip_conflict;
}

/* make the common DHCP message */
void makeDHCPMSG(void) {
  uint8_t bk_mac[6];
  uint8_t *ptmp;
  uint8_t i;
  getSHAR(bk_mac);
  pDHCPMSG->op = DHCP_BOOTREQUEST;
  pDHCPMSG->htype = DHCP_HTYPE10MB;
  pDHCPMSG->hlen = DHCP_HLENETHERNET;
  pDHCPMSG->hops = DHCP_HOPS;
  ptmp = (uint8_t *)(&pDHCPMSG->xid);
  *(ptmp + 0) = (uint8_t)((DHCP_XID & 0xFF000000) >> 24);
  *(ptmp + 1) = (uint8_t)((DHCP_XID & 0x00FF0000) >> 16);
  *(ptmp + 2) = (uint8_t)((DHCP_XID & 0x0000FF00) >> 8);
  *(ptmp + 3) = (uint8_t)((DHCP_XID & 0x000000FF) >> 0);
  pDHCPMSG->secs = DHCP_SECS;
  ptmp = (uint8_t *)(&pDHCPMSG->flags);
  *(ptmp + 0) = (uint8_t)((DHCP_FLAGSBROADCAST & 0xFF00) >> 8);
  *(ptmp + 1) = (uint8_t)((DHCP_FLAGSBROADCAST & 0x00FF) >> 0);

  pDHCPMSG->ciaddr[0] = 0;
  pDHCPMSG->ciaddr[1] = 0;
  pDHCPMSG->ciaddr[2] = 0;
  pDHCPMSG->ciaddr[3] = 0;

  pDHCPMSG->yiaddr[0] = 0;
  pDHCPMSG->yiaddr[1] = 0;
  pDHCPMSG->yiaddr[2] = 0;
  pDHCPMSG->yiaddr[3] = 0;

  pDHCPMSG->siaddr[0] = 0;
  pDHCPMSG->siaddr[1] = 0;
  pDHCPMSG->siaddr[2] = 0;
  pDHCPMSG->siaddr[3] = 0;

  pDHCPMSG->giaddr[0] = 0;
  pDHCPMSG->giaddr[1] = 0;
  pDHCPMSG->giaddr[2] = 0;
  pDHCPMSG->giaddr[3] = 0;

  pDHCPMSG->chaddr[0] = DHCP_CHADDR[0];
  pDHCPMSG->chaddr[1] = DHCP_CHADDR[1];
  pDHCPMSG->chaddr[2] = DHCP_CHADDR[2];
  pDHCPMSG->chaddr[3] = DHCP_CHADDR[3];
  pDHCPMSG->chaddr[4] = DHCP_CHADDR[4];
  pDHCPMSG->chaddr[5] = DHCP_CHADDR[5];

  for (i = 6; i < 16; i++) pDHCPMSG->chaddr[i] = 0;
  for (i = 0; i < 64; i++) pDHCPMSG->sname[i] = 0;
  for (i = 0; i < 128; i++) pDHCPMSG->file[i] = 0;

  // MAGIC_COOKIE
  pDHCPMSG->OPT[0] = (uint8_t)((MAGIC_COOKIE & 0xFF000000) >> 24);
  pDHCPMSG->OPT[1] = (uint8_t)((MAGIC_COOKIE & 0x00FF0000) >> 16);
  pDHCPMSG->OPT[2] = (uint8_t)((MAGIC_COOKIE & 0x0000FF00) >> 8);
  pDHCPMSG->OPT[3] = (uint8_t)(MAGIC_COOKIE & 0x000000FF) >> 0;
}

/* SEND DHCP DISCOVER */
void send_DHCP_DISCOVER(void) {
  uint16_t i;
  uint8_t ip[4];
  uint16_t k = 0;

  makeDHCPMSG();
  DHCP_SIP[0] = 0;
  DHCP_SIP[1] = 0;
  DHCP_SIP[2] = 0;
  DHCP_SIP[3] = 0;
  DHCP_REAL_SIP[0] = 0;
  DHCP_REAL_SIP[1] = 0;
  DHCP_REAL_SIP[2] = 0;
  DHCP_REAL_SIP[3] = 0;

  k = 4;  // because MAGIC_COOKIE already made by makeDHCPMSG()

  // Option Request Param
  pDHCPMSG->OPT[k++] = dhcpMessageType;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_DISCOVER;

  // Client identifier
  pDHCPMSG->OPT[k++] = dhcpClientIdentifier;
  pDHCPMSG->OPT[k++] = 0x07;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[0];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[1];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[2];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[3];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[4];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[5];

  // host name
  pDHCPMSG->OPT[k++] = hostName;
  pDHCPMSG->OPT[k++] = 0;  // fill zero length of hostname
  for (i = 0; HOST_NAME[i] != 0; i++) pDHCPMSG->OPT[k++] = HOST_NAME[i];
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[3] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[3]);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[4] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[4]);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[5] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[5]);
  pDHCPMSG->OPT[k - (i + 6 + 1)] = i + 6;  // length of hostname

  pDHCPMSG->OPT[k++] = dhcpParamRequest;
  pDHCPMSG->OPT[k++] = 0x06;  // length of request
  pDHCPMSG->OPT[k++] = subnetMask;
  pDHCPMSG->OPT[k++] = routersOnSubnet;
  pDHCPMSG->OPT[k++] = dns;
  pDHCPMSG->OPT[k++] = domainName;
  pDHCPMSG->OPT[k++] = dhcpT1value;
  pDHCPMSG->OPT[k++] = dhcpT2value;
  pDHCPMSG->OPT[k++] = endOption;

  for (i = k; i < OPT_SIZE; i++) pDHCPMSG->OPT[i] = 0;

  // send broadcasting packet
  ip[0] = 255;
  ip[1] = 255;
  ip[2] = 255;
  ip[3] = 255;

#ifdef _DHCP_DEBUG_
  printf("> Send DHCP_DISCOVER\r\n");
#endif

  WIZCHIP_EXPORT(sendto)
  (DHCP_SOCKET, (uint8_t *)pDHCPMSG, RIP_MSG_SIZE, ip, DHCP_SERVER_PORT,
   DHCP_WAIT_TIME);
}

/* SEND DHCP REQUEST */
void send_DHCP_REQUEST(void) {
  int i;
  uint8_t ip[4];
  uint16_t k = 0;

  makeDHCPMSG();

  if (dhcp_state == STATE_DHCP_LEASED || dhcp_state == STATE_DHCP_REREQUEST) {
    *((uint8_t *)(&pDHCPMSG->flags)) = ((DHCP_FLAGSUNICAST & 0xFF00) >> 8);
    *((uint8_t *)(&pDHCPMSG->flags) + 1) = (DHCP_FLAGSUNICAST & 0x00FF);
    pDHCPMSG->ciaddr[0] = DHCP_allocated_ip[0];
    pDHCPMSG->ciaddr[1] = DHCP_allocated_ip[1];
    pDHCPMSG->ciaddr[2] = DHCP_allocated_ip[2];
    pDHCPMSG->ciaddr[3] = DHCP_allocated_ip[3];
    ip[0] = DHCP_SIP[0];
    ip[1] = DHCP_SIP[1];
    ip[2] = DHCP_SIP[2];
    ip[3] = DHCP_SIP[3];
  } else {
    ip[0] = 255;
    ip[1] = 255;
    ip[2] = 255;
    ip[3] = 255;
  }

  k = 4;  // because MAGIC_COOKIE already made by makeDHCPMSG()

  // Option Request Param.
  pDHCPMSG->OPT[k++] = dhcpMessageType;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_REQUEST;

  pDHCPMSG->OPT[k++] = dhcpClientIdentifier;
  pDHCPMSG->OPT[k++] = 0x07;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[0];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[1];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[2];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[3];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[4];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[5];

  if (ip[3] == 255)  // if(dchp_state == STATE_DHCP_LEASED || dchp_state ==
                     // DHCP_REREQUEST_STATE)
  {
    pDHCPMSG->OPT[k++] = dhcpRequestedIPaddr;
    pDHCPMSG->OPT[k++] = 0x04;
    pDHCPMSG->OPT[k++] = DHCP_allocated_ip[0];
    pDHCPMSG->OPT[k++] = DHCP_allocated_ip[1];
    pDHCPMSG->OPT[k++] = DHCP_allocated_ip[2];
    pDHCPMSG->OPT[k++] = DHCP_allocated_ip[3];

    pDHCPMSG->OPT[k++] = dhcpServerIdentifier;
    pDHCPMSG->OPT[k++] = 0x04;
    pDHCPMSG->OPT[k++] = DHCP_SIP[0];
    pDHCPMSG->OPT[k++] = DHCP_SIP[1];
    pDHCPMSG->OPT[k++] = DHCP_SIP[2];
    pDHCPMSG->OPT[k++] = DHCP_SIP[3];
  }

  // host name
  pDHCPMSG->OPT[k++] = hostName;
  pDHCPMSG->OPT[k++] = 0;  // length of hostname
  for (i = 0; HOST_NAME[i] != 0; i++) pDHCPMSG->OPT[k++] = HOST_NAME[i];
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[3] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[3]);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[4] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[4]);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[5] >> 4);
  pDHCPMSG->OPT[k++] = NibbleToHex(DHCP_CHADDR[5]);
  pDHCPMSG->OPT[k - (i + 6 + 1)] = i + 6;  // length of hostname

  pDHCPMSG->OPT[k++] = dhcpParamRequest;
  pDHCPMSG->OPT[k++] = 0x08;
  pDHCPMSG->OPT[k++] = subnetMask;
  pDHCPMSG->OPT[k++] = routersOnSubnet;
  pDHCPMSG->OPT[k++] = dns;
  pDHCPMSG->OPT[k++] = domainName;
  pDHCPMSG->OPT[k++] = dhcpT1value;
  pDHCPMSG->OPT[k++] = dhcpT2value;
  pDHCPMSG->OPT[k++] = performRouterDiscovery;
  pDHCPMSG->OPT[k++] = staticRoute;
  pDHCPMSG->OPT[k++] = endOption;

  for (i = k; i < OPT_SIZE; i++) pDHCPMSG->OPT[i] = 0;

#ifdef _DHCP_DEBUG_
  printf("> Send DHCP_REQUEST\r\n");
#endif

  WIZCHIP_EXPORT(sendto)
  (DHCP_SOCKET, (uint8_t *)pDHCPMSG, RIP_MSG_SIZE, ip, DHCP_SERVER_PORT,
   DHCP_WAIT_TIME);
}

/* SEND DHCP DHCPDECLINE */
void send_DHCP_DECLINE(void) {
  int i;
  uint8_t ip[4];
  uint16_t k = 0;

  makeDHCPMSG();

  k = 4;  // because MAGIC_COOKIE already made by makeDHCPMSG()

  *((uint8_t *)(&pDHCPMSG->flags)) = ((DHCP_FLAGSUNICAST & 0xFF00) >> 8);
  *((uint8_t *)(&pDHCPMSG->flags) + 1) = (DHCP_FLAGSUNICAST & 0x00FF);

  // Option Request Param.
  pDHCPMSG->OPT[k++] = dhcpMessageType;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_DECLINE;

  pDHCPMSG->OPT[k++] = dhcpClientIdentifier;
  pDHCPMSG->OPT[k++] = 0x07;
  pDHCPMSG->OPT[k++] = 0x01;
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[0];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[1];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[2];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[3];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[4];
  pDHCPMSG->OPT[k++] = DHCP_CHADDR[5];

  pDHCPMSG->OPT[k++] = dhcpRequestedIPaddr;
  pDHCPMSG->OPT[k++] = 0x04;
  pDHCPMSG->OPT[k++] = DHCP_allocated_ip[0];
  pDHCPMSG->OPT[k++] = DHCP_allocated_ip[1];
  pDHCPMSG->OPT[k++] = DHCP_allocated_ip[2];
  pDHCPMSG->OPT[k++] = DHCP_allocated_ip[3];

  pDHCPMSG->OPT[k++] = dhcpServerIdentifier;
  pDHCPMSG->OPT[k++] = 0x04;
  pDHCPMSG->OPT[k++] = DHCP_SIP[0];
  pDHCPMSG->OPT[k++] = DHCP_SIP[1];
  pDHCPMSG->OPT[k++] = DHCP_SIP[2];
  pDHCPMSG->OPT[k++] = DHCP_SIP[3];

  pDHCPMSG->OPT[k++] = endOption;

  for (i = k; i < OPT_SIZE; i++) pDHCPMSG->OPT[i] = 0;

  // send broadcasting packet
  ip[0] = 0xFF;
  ip[1] = 0xFF;
  ip[2] = 0xFF;
  ip[3] = 0xFF;

#ifdef _DHCP_DEBUG_
  printf("\r\n> Send DHCP_DECLINE\r\n");
#endif

  WIZCHIP_EXPORT(sendto)
  (DHCP_SOCKET, (uint8_t *)pDHCPMSG, RIP_MSG_SIZE, ip, DHCP_SERVER_PORT,
   DHCP_WAIT_TIME);
}

/* PARSE REPLY pDHCPMSG */
int8_t parseDHCPMSG(void) {
  uint8_t svr_addr[6];
  uint16_t svr_port;
  uint16_t len;

  uint8_t *p;
  uint8_t *e;
  uint8_t type = 0;
  uint8_t opt_len;
  int errno = 0;

  if ((len = getSn_RX_RSR(DHCP_SOCKET)) > 0) {
    len = WIZCHIP_EXPORT(recvfrom)(DHCP_SOCKET, (uint8_t *)pDHCPMSG, len,
                                   svr_addr, &svr_port, DHCP_WAIT_TIME);
#ifdef _DHCP_DEBUG_
    printf("DHCP message : %d.%d.%d.%d(%d) %d received. \r\n", svr_addr[0],
           svr_addr[1], svr_addr[2], svr_addr[3], svr_port, len);
#endif
  } else
    return 0;
  if (svr_port == DHCP_SERVER_PORT) {
    // compare mac address
    if ((pDHCPMSG->chaddr[0] != DHCP_CHADDR[0]) ||
        (pDHCPMSG->chaddr[1] != DHCP_CHADDR[1]) ||
        (pDHCPMSG->chaddr[2] != DHCP_CHADDR[2]) ||
        (pDHCPMSG->chaddr[3] != DHCP_CHADDR[3]) ||
        (pDHCPMSG->chaddr[4] != DHCP_CHADDR[4]) ||
        (pDHCPMSG->chaddr[5] != DHCP_CHADDR[5])) {
#ifdef _DHCP_DEBUG_
      printf("No My DHCP Message. This message is ignored.\r\n");
#endif
      return 0;
    }
    // compare DHCP server ip address
    if ((DHCP_SIP[0] != 0) || (DHCP_SIP[1] != 0) || (DHCP_SIP[2] != 0) ||
        (DHCP_SIP[3] != 0)) {
      if (((svr_addr[0] != DHCP_SIP[0]) || (svr_addr[1] != DHCP_SIP[1]) ||
           (svr_addr[2] != DHCP_SIP[2]) || (svr_addr[3] != DHCP_SIP[3])) &&
          ((svr_addr[0] != DHCP_REAL_SIP[0]) ||
           (svr_addr[1] != DHCP_REAL_SIP[1]) ||
           (svr_addr[2] != DHCP_REAL_SIP[2]) ||
           (svr_addr[3] != DHCP_REAL_SIP[3]))) {
#ifdef _DHCP_DEBUG_
        printf(
            "Another DHCP sever send a response message. This is ignored.\r\n");
#endif
        return 0;
      }
    }
    p = (uint8_t *)(&pDHCPMSG->op);
    p = p + 240;  // 240 = sizeof(RIP_MSG) + MAGIC_COOKIE size in RIP_MSG.opt -
                  // sizeof(RIP_MSG.opt)
    e = p + (len - 240);

    while (p < e) {
      switch (*p) {
        case endOption:
          p = e;  // for break while(p < e)
          break;
        case padOption:
          p++;
          break;
        case dhcpMessageType:
          p++;
          p++;
          type = *p++;
          break;
        case subnetMask:
          p++;
          p++;
          DHCP_allocated_sn[0] = *p++;
          DHCP_allocated_sn[1] = *p++;
          DHCP_allocated_sn[2] = *p++;
          DHCP_allocated_sn[3] = *p++;
          break;
        case routersOnSubnet:
          p++;
          opt_len = *p++;
          DHCP_allocated_gw[0] = *p++;
          DHCP_allocated_gw[1] = *p++;
          DHCP_allocated_gw[2] = *p++;
          DHCP_allocated_gw[3] = *p++;
          p = p + (opt_len - 4);
          break;
        case dns:
          p++;
          opt_len = *p++;
          DHCP_allocated_dns[0] = *p++;
          DHCP_allocated_dns[1] = *p++;
          DHCP_allocated_dns[2] = *p++;
          DHCP_allocated_dns[3] = *p++;
          p = p + (opt_len - 4);
          break;
        case dhcpIPaddrLeaseTime:
          p++;
          opt_len = *p++;
          dhcp_lease_time = *p++;
          dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
          dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
          dhcp_lease_time = (dhcp_lease_time << 8) + *p++;
#ifdef _DHCP_DEBUG_
          dhcp_lease_time = 10;
#endif
          break;
        case dhcpServerIdentifier:
          p++;
          opt_len = *p++;
          DHCP_SIP[0] = *p++;
          DHCP_SIP[1] = *p++;
          DHCP_SIP[2] = *p++;
          DHCP_SIP[3] = *p++;
          DHCP_REAL_SIP[0] = svr_addr[0];
          DHCP_REAL_SIP[1] = svr_addr[1];
          DHCP_REAL_SIP[2] = svr_addr[2];
          DHCP_REAL_SIP[3] = svr_addr[3];
          break;
        default:
          p++;
          opt_len = *p++;
          p += opt_len;
          break;
      }  // switch
    }    // while
  }      // if
  return type;
}

uint8_t DHCP_run(void) {
  uint8_t type;
  uint8_t ret;

  if (dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;

  if (getSn_SR(DHCP_SOCKET) != SOCK_UDP) WIZCHIP_EXPORT(socket)
  (DHCP_SOCKET, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);

  ret = DHCP_RUNNING;
  type = parseDHCPMSG();
  switch (dhcp_state) {
    case STATE_DHCP_INIT:
      DHCP_allocated_ip[0] = 0;
      DHCP_allocated_ip[1] = 0;
      DHCP_allocated_ip[2] = 0;
      DHCP_allocated_ip[3] = 0;
      send_DHCP_DISCOVER();
      dhcp_state = STATE_DHCP_DISCOVER;
      break;
    case STATE_DHCP_DISCOVER:
      if (type == DHCP_OFFER) {
#ifdef _DHCP_DEBUG_
        printf("> Receive DHCP_OFFER\r\n");
#endif
        DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
        DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
        DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
        DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3];

        send_DHCP_REQUEST();
        dhcp_state = STATE_DHCP_REQUEST;
      } else
        ret = check_DHCP_timeout();
      break;

    case STATE_DHCP_REQUEST:
      if (type == DHCP_ACK) {
#ifdef _DHCP_DEBUG_
        printf("> Receive DHCP_ACK\r\n");
#endif
        if (check_DHCP_leasedIP()) {
          // Network info assignment from DHCP
          dhcp_ip_assign();
          reset_DHCP_timeout();

          dhcp_state = STATE_DHCP_LEASED;
        } else {
          // IP address conflict occurred
          reset_DHCP_timeout();
          dhcp_ip_conflict();
          dhcp_state = STATE_DHCP_INIT;
        }
      } else if (type == DHCP_NAK) {
#ifdef _DHCP_DEBUG_
        printf("> Receive DHCP_NACK\r\n");
#endif

        reset_DHCP_timeout();

        dhcp_state = STATE_DHCP_DISCOVER;
      } else
        ret = check_DHCP_timeout();
      break;

    case STATE_DHCP_LEASED:
      ret = DHCP_IP_LEASED;
      if ((dhcp_lease_time != INFINITE_LEASETIME) &&
          ((dhcp_lease_time / 2) < dhcp_tick_1s)) {
#ifdef _DHCP_DEBUG_
        printf("> Maintains the IP address \r\n");
#endif

        type = 0;
        OLD_allocated_ip[0] = DHCP_allocated_ip[0];
        OLD_allocated_ip[1] = DHCP_allocated_ip[1];
        OLD_allocated_ip[2] = DHCP_allocated_ip[2];
        OLD_allocated_ip[3] = DHCP_allocated_ip[3];

        DHCP_XID++;

        send_DHCP_REQUEST();

        reset_DHCP_timeout();

        dhcp_state = STATE_DHCP_REREQUEST;
      }
      break;

    case STATE_DHCP_REREQUEST:
      ret = DHCP_IP_LEASED;
      if (type == DHCP_ACK) {
        dhcp_retry_count = 0;
        if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
            OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
            OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
            OLD_allocated_ip[3] != DHCP_allocated_ip[3]) {
          ret = DHCP_IP_CHANGED;
          dhcp_ip_update();
#ifdef _DHCP_DEBUG_
          printf(">IP changed.\r\n");
#endif
        }
#ifdef _DHCP_DEBUG_
        else
          printf(">IP is continued.\r\n");
#endif
        reset_DHCP_timeout();
        dhcp_state = STATE_DHCP_LEASED;
      } else if (type == DHCP_NAK) {
#ifdef _DHCP_DEBUG_
        printf("> Receive DHCP_NACK, Failed to maintain ip\r\n");
#endif

        reset_DHCP_timeout();

        dhcp_state = STATE_DHCP_DISCOVER;
      } else
        ret = check_DHCP_timeout();
      break;
    default:
      break;
  }

  return ret;
}

void DHCP_stop(void) {
  WIZCHIP_EXPORT(close)
  (DHCP_SOCKET);
  dhcp_state = STATE_DHCP_STOP;
}

uint8_t check_DHCP_timeout(void) {
  uint8_t ret = DHCP_RUNNING;

  if (dhcp_retry_count < MAX_DHCP_RETRY) {
    if (dhcp_tick_next < dhcp_tick_1s) {
      switch (dhcp_state) {
        case STATE_DHCP_DISCOVER:
#ifdef _DHCP_DEBUG_
          printf("<<timeout>> state : STATE_DHCP_DISCOVER\r\n");
#endif
          send_DHCP_DISCOVER();
          break;

        case STATE_DHCP_REQUEST:
#ifdef _DHCP_DEBUG_
          printf("<<timeout>> state : STATE_DHCP_REQUEST\r\n");
#endif
          send_DHCP_REQUEST();
          break;

        case STATE_DHCP_REREQUEST:
#ifdef _DHCP_DEBUG_
          printf("<<timeout>> state : STATE_DHCP_REREQUEST\r\n");
#endif
          send_DHCP_REQUEST();
          break;

        default:
          break;
      }

      dhcp_tick_1s = 0;
      dhcp_tick_next = dhcp_tick_1s + DHCP_WAIT_TIME;
      dhcp_retry_count++;
    }
  } else {  // timeout occurred

    switch (dhcp_state) {
      case STATE_DHCP_DISCOVER:
        dhcp_state = STATE_DHCP_INIT;
        ret = DHCP_FAILED;
        break;
      case STATE_DHCP_REQUEST:
      case STATE_DHCP_REREQUEST:
        send_DHCP_DISCOVER();
        dhcp_state = STATE_DHCP_DISCOVER;
        break;
      default:
        break;
    }
    reset_DHCP_timeout();
  }
  return ret;
}

int8_t check_DHCP_leasedIP(void) {
  uint8_t tmp;
  int32_t ret;

  // WIZchip RCR value changed for ARP Timeout count control
  tmp = getRCR();
  setRCR(0x03);

  // IP conflict detection : ARP request - ARP reply
  // Broadcasting ARP Request for check the IP conflict using UDP sendto()
  // function
  ret = WIZCHIP_EXPORT(sendto)(DHCP_SOCKET, (uint8_t *)"CHECK_IP_CONFLICT", 17,
                               DHCP_allocated_ip, 5000, DHCP_WAIT_TIME);

  // RCR value restore
  setRCR(tmp);

  if (ret == SOCKERR_TIMEOUT) {
    // UDP send Timeout occurred : allocated IP address is unique, DHCP Success

#ifdef _DHCP_DEBUG_
    printf("\r\n> Check leased IP - OK\r\n");
#endif

    return 1;
  } else {
    // Received ARP reply or etc : IP address conflict occur, DHCP Failed
    send_DHCP_DECLINE();

    ret = dhcp_tick_1s;
    while ((dhcp_tick_1s - ret) < 2)
      ;  // wait for 1s over; wait to complete to send DECLINE message;

    return 0;
  }
}

void DHCP_init(uint8_t s, uint8_t *buf) {
  uint8_t zeroip[4] = {0, 0, 0, 0};
  getSHAR(DHCP_CHADDR);
  if ((DHCP_CHADDR[0] | DHCP_CHADDR[1] | DHCP_CHADDR[2] | DHCP_CHADDR[3] |
       DHCP_CHADDR[4] | DHCP_CHADDR[5]) == 0x00) {
    // assigning temporary mac address, you should be set SHAR before call this
    // function.
    DHCP_CHADDR[0] = 0x00;
    DHCP_CHADDR[1] = 0x08;
    DHCP_CHADDR[2] = 0xdc;
    DHCP_CHADDR[3] = 0x00;
    DHCP_CHADDR[4] = 0x00;
    DHCP_CHADDR[5] = 0x00;
    setSHAR(DHCP_CHADDR);
  }

  DHCP_SOCKET = s;  // SOCK_DHCP
  pDHCPMSG = (RIP_MSG *)buf;
  DHCP_XID = 0x12345678;
  {
    DHCP_XID += DHCP_CHADDR[3];
    DHCP_XID += DHCP_CHADDR[4];
    DHCP_XID += DHCP_CHADDR[5];
    DHCP_XID += (DHCP_CHADDR[3] ^ DHCP_CHADDR[4] ^ DHCP_CHADDR[5]);
  }
  // WIZchip Netinfo Clear
  setSIPR(zeroip);
  setGAR(zeroip);

  reset_DHCP_timeout();
  dhcp_state = STATE_DHCP_INIT;
}

/* Reset the DHCP timeout count and retry count. */
void reset_DHCP_timeout(void) {
  dhcp_tick_1s = 0;
  dhcp_tick_next = DHCP_WAIT_TIME;
  dhcp_retry_count = 0;
}

void DHCP_time_handler(void) { dhcp_tick_1s++; }

void getIPfromDHCP(uint8_t *ip) {
  ip[0] = DHCP_allocated_ip[0];
  ip[1] = DHCP_allocated_ip[1];
  ip[2] = DHCP_allocated_ip[2];
  ip[3] = DHCP_allocated_ip[3];
}

void getGWfromDHCP(uint8_t *ip) {
  ip[0] = DHCP_allocated_gw[0];
  ip[1] = DHCP_allocated_gw[1];
  ip[2] = DHCP_allocated_gw[2];
  ip[3] = DHCP_allocated_gw[3];
}

void getSNfromDHCP(uint8_t *ip) {
  ip[0] = DHCP_allocated_sn[0];
  ip[1] = DHCP_allocated_sn[1];
  ip[2] = DHCP_allocated_sn[2];
  ip[3] = DHCP_allocated_sn[3];
}

void getDNSfromDHCP(uint8_t *ip) {
  ip[0] = DHCP_allocated_dns[0];
  ip[1] = DHCP_allocated_dns[1];
  ip[2] = DHCP_allocated_dns[2];
  ip[3] = DHCP_allocated_dns[3];
}

uint32_t getDHCPLeasetime(void) { return dhcp_lease_time; }

char NibbleToHex(uint8_t nibble) {
  nibble &= 0x0F;
  if (nibble <= 9)
    return nibble + '0';
  else
    return nibble + ('A' - 0x0A);
}
